// locutor
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id$
package locutor.dg

import java.io.File
import java.net.{URL, MalformedURLException}
import java.text.MessageFormat.{format}

import scaup.data._
import scaup.Views._

case class DGError(message: String) extends Throwable(message)

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.1 */
private [dg] class Builder(base: File, id: Option[String]) {
  import scaup.util.parsing.dot._
  import scaup.util.parsing.dot.{Subgraph => SG}
  
  val unquote = """^(?:[^"]*")?([^"]*)(?:"[^"]*)?$""".r
  
  private val g: DG = new DG(id)
  
  implicit private val attributes = Map.empty[String, String]
  
  def +=(statement: Statement): this.type = statement match {
    case Node(id, as @_*)                           => g.η(id)(transform(as)); this
    case Edge(as, Node(sid, _@_*), Node(tid, _@_*)) => g.ℯ(g.η(sid), g.η(tid))(transform(as)); this
    case Edge(as, Node(nid, _@_*), SG(None, ss@_*)) => flatten(nid, ss, as); this      
    case Edge(as, SG(None, ss@_*), Node(nid, _@_*)) => flatten(ss, nid, as); this
    case Edge(as, SG(None, ss@_*), SG(None, tt@_*)) => flatten(ss, tt, as); this
    case Attrs(kind: String, as @_*)                => g.æ(kind)(transform(as)); this
    case _  => throw new DGError(format("DOT graph transformation error in ''{0}''", statement))
  }
  
  //@todo resolve URLs starting with ``^/''
  //@todo split URL attribute into id := (URL, XPath)
  def transform(attributes: Seq[Attr]) = (Map.empty[String, String] /: attributes)((m, a) => (a.name, a.value) match {
    case (k, v) if k == "URL" => m + {(k, v match {case unquote(u) => (base.toURI resolve u).toString})}
    case (k, v)               => m + {(k, v match {case unquote(u) => u})}
  })
  
  def flatten(source: String, targets: Seq[Statement], attributes: Seq[Attr]) {
    val sg = (new Builder(base, None) /: targets)((g, s) => g += s).result
    if(sg.E isEmpty)
      sg.V foreach (v => g.ℯ(g.η(source), g.η(v.id)(v.label))(transform(attributes)))
    else throw new DGError(format("DOT graph transformation error in ''{0}''", targets))
  }
  
  def flatten(sources: Seq[Statement], target: String, attributes: Seq[Attr]) {
    val sg = (new Builder(base, None) /: sources)((g, s) => g += s).result
    if(sg.E isEmpty)
      sg.V foreach (v => g.ℯ(g.η(v.id)(v.label), g.η(target))(transform(attributes)))
    else throw new DGError(format("DOT graph transformation error in ''{0}''", sources))
  }
  
  def flatten(sources: Seq[Statement], targets: Seq[Statement], attributes: Seq[Attr]) {
    val sg = (new Builder(base, None) /: sources)((g, s) => g += s).result
    if(sg.E isEmpty)
      sg.V foreach (u => flatten(u.id, targets, attributes))                      
    else throw new DGError(format("DOT graph transformation error in ''{0}''", sources))
  }
  
  def result() = g
}

///** 
// * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
// * @version 0.0.1 */
//private [dg] object Verifier {
//  val unquote = """"*(file:/[.[^"]]*)"*""".r
//  
//  def verify(g: DG): Option[DG] = hasReference(g) match {
//    case true  => Some(g)
//    case false => None
//  }
//  
//  private def hasReference(g: DG): Boolean = g.A.get("URL") match {
//    case Some(ref) => try { ref.toURL; true } catch { case _: MalformedURLException => false }
//    case _         => false
//  }
//}

/** A dependency graph.
 * 
 *  A dependency graph is a directed, classified, labeled multi-graph.
 *  
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.1 */
object DG {
  import scaup.util.parsing.dot.{DOT, Graph}
  
  implicit val empty_attributes = Map.empty[String, String]
  
  def apply(dot: File): DG = apply(dot.af, dot.content)
  def apply(dot: String): DG = apply(".".toFile, dot)
  
  def apply(base: File, dot: String) = DOT(dot) match {
    case Graph(false, true, id, ss@_*) => (new Builder(base, id) /: ss)((g, s) => g += s).result
    case _ => throw new DGError(format("DOT graph transformation error in ''{0}''", dot))
  }  
}

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.1 */
class DG(id: Option[String]) extends MultiGraph
  with MultiGraph#NodeLabels[Map[String, String]] //@todo nodes should be mapped by (URL, XPath)
  with MultiGraph#EdgeLabels[Map[String, String]]
  with MultiGraph#Directions {
  
  type A = Map[String, String]
  
  private var attributes = Map.empty[String, A] withDefaultValue(Map())
  private var nodes = Map.empty[String, V]
  private var edges: List[E] = Nil //@todo edges should be mapped by their type  
  
  def æ(kind: String)(implicit attributes: A): A = {
    this.attributes = this.attributes + {(kind, this.attributes get kind match {
      case Some(as) => as ++ attributes
      case None     => attributes
    })}
    this.attributes(kind)
  }
                              
  type V = INFOM
  
  private[DG] case class INFOM(id: String, label: A) extends Node { self: V =>
    def ->(v: V): E = this -@> v
    def -@>(v: V)(implicit attributes: A): E = ℯ(this, v)
    
    def inDegree: Int = incoming.size
    def incoming: Set[E] = E filter {case Dependency(_, _, target) if target == this => true case _ => false}
      
    def outDegree: Int = outgoing.size
    def outgoing: Set[E] = E filter {case Dependency(source, _, _) if source == this => true case _ => false}
        
    def degree: Int = inDegree + outDegree    
  } 
  
  def η(id: String)(implicit attributes: A): V = {
    nodes = nodes + {(id, nodes get id match {
      case Some(v) => INFOM(id, v.label ++ attributes)
      case None    => INFOM(id, attributes)   
    })}
    //@todo in case of edge components are declared/ extended after edge declaration all respective edges
    // have to be updated.  For example:  digraph G { a -> b; a [label=A]; b [label=B] }  
    // Note: Here we are (lazy, hence) updating all edges.  Maybe we should restrict
    // node declarations to occure /before/ edge declaration.
    edges = edges map {case Dependency(source, label, target) => Dependency(nodes(source.id), label, nodes(target.id))}
    nodes(id)
  }
  
  type E = Dependency
  
  private[DG] case class Dependency(override val source: V, label: A, override val target: V) extends super.Edge(source, target)
  
  def ℯ(source: V, target: V)(implicit attributes: A): E = {
    val d = Dependency(source, attributes, target)
    edges ::= d
    d
  }
  
  def A = attributes("graph")
  
  def V = Set.empty[V] ++ nodes.values
  
  def E = Set.empty[E] ++ edges  
}
